/*
 * Author: nicolas.bredeche(@)lri.fr
 * Created on 2 sept. 2005 - rewritten 20061210
 *
 * parameter set repository
 *
 * TODO: utiliser existProperty pour voir si une cl� existe d�ja -- et si oui, que faire? si erreur: on peut jamais remplacer une valeur... sinon, on renvoi un objet (quelquesoit la liste), on efface l'ancienne, on ajoute la nouvelle.
 * TODO: optimize Object list internal storage.
 *  
 */

/**
 * This class is inherited from the Property class and extended so as to handle any Object registration. 
 * Depending on the type of the registered target, the value is stored in the Property internal structure 
 * (i.e. standard Property storing method - for String,Integer,Boolean,Double) or ParameterSet internal 
 * structure (i.e. Object general storing methods - for any other Object).
 * 
 * The idea is to be able to behave as a classic Properties Object and still to store Object references
 * that are directly usable. Note that in case of extensive use of a stored object, one should better 
 * locally store the Object (multiple calls = multiple searches through internal list in ParameterSet).
 * 
 * The entry points for this method are: 
 * 	- setProperty : store an object
 *  - getProperty : return the String value of an object
 *  - getObjectProperty : return the Object value of an object (useful)
 * 
 * Using ParameterSet extensively is *not* advised: the get* methods searches through the two internal lists 
 * (String-typed Properties and Object-typed ParameterSet lists). Therefore ParameterSet Object is more general 
 * but slower than Properties Object. It is provided for handy rapid prototyping but should be optimized for heavy 
 * code (i.e. storing Method pointers or values internaly for the relevant objects). An easy optimization option
 * is to inherit from ParameterSet and create dedicated fields and accessing methods for storing frequently used objects
 * (e.g. in a picoevo enironment, Variation and Selection Operators should be stored as is with dedicated get 
 * accessing method, avoid list searches). (complexity is sizeoflists*nbaccessingcalls at worst - linear growth along one params.).
 * 
 * the setDebug method makes it possible to throw an exception if a key does not exists when using getProperty(String) 
 * *** Defaut value is FALSE *** (false means full Properties class behavior compatibility ; true means if key is not found then error)
 * 
 * TODO: not all methods from Properties are rewritten (use only overridden method)
 * TODO: inherited class for optimizing picoevo Variation/Selection Op.
 * 
 */

package picoevo.core.evolution;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Properties;

import picoevo.toolbox.Display;

public class ParameterSet extends Properties implements Cloneable {

	// ### Data ###

	private ArrayList _keys = new ArrayList();
	private ArrayList _values = new ArrayList();
	private String _name = "(no name)";

	private boolean _debug = false; // true: generates exception if key not

	// found; false: compatibility with
	// Properties

	// ### Constructors ###

	public ParameterSet() {
	}

	public ParameterSet(String __name) {
		this._name = __name;
	}

	// ### setProperty methods ###

	public Object setProperty(String __key, Object __value) {
		Object returnValue = null;
		if (this.contains(__key) == true) {
			returnValue = this.getProperty(__key);
			this.remove(__key);
		}

		_keys.add(__key);
		_values.add(__value);
		return returnValue;
	}

	/**
	 * override the default Properties'setProperty method. return value is
	 * provided for compatibility.
	 */
	@Override
	public Object setProperty(String __key, String __value) {
		Object returnValue = null;
		if (this.contains(__key) == true) {
			returnValue = this.getProperty(__key);
			this.remove(__key);
		}

		super.put(__key, __value);

		return returnValue;
	}

	public Object setProperty(String __key, int __value) {
		return setProperty(__key, String.valueOf(__value));
	}

	public Object setProperty(String __key, double __value) {
		return setProperty(__key, String.valueOf(__value));
	}

	public Object setProperty(String __key, boolean __value) {
		return setProperty(__key, String.valueOf(__value));
	}

	/**
	 * override the hashtable's put method -- put method should not be used --
	 * use setProperty methods instead. n! devnotes: put cannot be overriden
	 * because of loadProperty methods
	 */
	/*
	 * public Object put (Object __key, Object __value) {
	 * Display.warning("ParameterSet - avoid using put method (use setProperty
	 * instead)."); try { throw new Exception(); } catch (Exception e) {
	 * e.printStackTrace(); } Display.critical("stop."); return null; }
	 */

	// ### getProperty methods ###
	/**
	 * Searches through *all* properties (object, string, boolean, int).
	 * Important issue : the ParameterSet is an extension of the Properties
	 * class that is not limited to storing String, but also makes it possible
	 * to store any kind of Object which can be used directly afterwards (with
	 * the appropriate cast). see also getProperty.
	 * 
	 * @param __key
	 *            name of the target property
	 * @return the Object value of the property
	 */
	public Object getObjectProperty(String __key) {
		// searches through Properties internal storage
		String target = super.getProperty(__key);
		if (target != null)
			return target;

		// searches through ParameterSet internal storage
		int i;
		for (i = 0; i != _keys.size(); i++)
			if ((((String) _keys.get(i)).compareTo(__key)) == 0)
				return (_values.get(i));

		// not found
		Display.warning("ParameterSet \"" + this._name + "\" : key [" + __key + "] does not exist.");
		if (_debug == true) {
			try {
				throw new Exception();
			} catch (Exception e) {
				e.printStackTrace();
			}
			Display.critical("stop.");
		}

		return null;
	}

	/**
	 * Searches through *all* properties (object, string, boolean, int -- differ
	 * from Properties class). see also getObjectProperty.
	 * 
	 * @param __key
	 *            name of the target property
	 * @return the String value of the property
	 */
	@Override
	public String getProperty(String __key) {
		// searches through Properties internal storage
		String target = super.getProperty(__key);
		if (target != null)
			return target;

		// searches through ParameterSet internal storage
		int i;
		for (i = 0; i != _keys.size(); i++)
			if ((((String) _keys.get(i)).compareTo(__key)) == 0)
				return ((_values.get(i)).toString());

		// not found
		Display.warning("ParameterSet \"" + this._name + "\" : key [" + __key + "] does not exist.");

		if (_debug == true) {
			try { // useful for user debugging.
				throw new Exception();
			} catch (Exception e) {
				e.printStackTrace();
			}
			Display.critical("stop.");
		}

		return null;
	}

	/**
	 * same as getProperty(String) method, but return the defaultValue if key
	 * not found.
	 */
	@Override
	public String getProperty(String __key, String __defaultValue) {
		// searches through Properties internal storage
		String target = super.getProperty(__key);
		if (target != null)
			return target;

		// searches through ParameterSet internal storage
		int i;
		for (i = 0; i != _keys.size(); i++)
			if ((((String) _keys.get(i)).compareTo(__key)) == 0)
				return ((_values.get(i)).toString());

		return __defaultValue;
	}

	public int getIntProperty(String __key) {
		return (Integer.parseInt(this.getProperty(__key)));
	}

	public double getDoubleProperty(String __key) {
		return (Double.parseDouble(this.getProperty(__key)));
	}

	public boolean getBooleanProperty(String __key) {
		String s = this.getProperty(__key);
		if (s == "true" || s == "TRUE")
			return (true);
		else if (s == "false" || s == "FALSE")
			return (false);
		else
			Display.critical(this.getClass().getName() + "::getBooleanProperty(.) - not a boolean");
		return false; // never used.
	}

	public String getStringProperty(String __key) {
		return ((this.getProperty(__key)));
	}

	// ### Other methods ###

	/**
	 * check if targeted key exists (check both Properties and ParameteSet
	 * lists)
	 */
	public boolean contains(String __key) {
		if (super.getProperty(__key) != null) // check Properties list
			return true;

		return containsObjectKey(__key); // check ParameterSet list
	}

	private boolean containsObjectKey(String __key) // only ParameterSet list
	{
		for (int i = 0; i != _keys.size(); i++)
			if ((((String) _keys.get(i)).compareTo(__key)) == 0)
				return true;
		return false;
	}

	/**
	 * remove key/value if any (search both Properties and ParameterSet lists).
	 * Returns false if no target found.
	 * 
	 * @param __key
	 */
	public boolean remove(String __key) {
		if (super.containsKey(__key) == true) {
			super.remove(__key);
			return true;
		}
		return removeObjectKey(__key);
	}

	private boolean removeObjectKey(String __key) {
		for (int i = 0; i != _keys.size(); i++)
			if ((((String) _keys.get(i)).compareTo(__key)) == 0) {
				this._keys.remove(i);
				this._values.remove(i);
				return true;
			}
		return false;
	}

	@Override
	public void clear() {
		super.clear();
		this._keys.clear();
		this._values.clear();
	}

	/**
	 * Load *properties* from a file -- warning, everything is loaded as a (key,
	 * string value) couple. After this, Properties'internal list may be
	 * changed, but ParameterSet'internal list remains unmodified.
	 * 
	 * @param fileName
	 */
	public void loadProperties(String fileName) {
		if (fileName.equals("")) {
			Display.warning("Loading Properties : filename empty.");
			return;
		}
		try {
			FileInputStream fis = new FileInputStream(fileName);
			this.load(fis);
			fis.close();
		} catch (IOException e) {
			e.printStackTrace();
			Display.warning("Loading Properties : File \"" + fileName + "\" not found.");
		}
	}

	// ### monitoring methods ###

	public void setDebug(boolean __value) {
		this._debug = __value;
	}

	public void displayInformation() {
		Display.info("\n### PROPERTIES and PARAMETER sets :\"" + this._name + "\" ###");
		Display.info("Listing all registered keys/values.");
		Display.info("{* Properties internal list *}");
		// Display.info(""+ super.toString());
		super.list(System.out);
		Display.info("{* ParameterSet internal list *}");
		for (int i = 0; i != _keys.size(); i++)
			Display.info("[" + ((String) _keys.get(i)) + "] => [" + _values.get(i) + "]");
		Display.info("{* end *}.");
	}

	public void displayInformation(String __outputfilename) {
		try {
			OutputStream propOut = new FileOutputStream(new File(__outputfilename));
			this.store(propOut, "file " + __outputfilename);
			BufferedWriter outputFile = new BufferedWriter(new FileWriter(__outputfilename, true));
			// outputFile.write(""+super.toString()+"\n"); // listing properties
			outputFile.write("\n");
			for (int i = 0; i != _keys.size(); i++)
				outputFile.write("[" + ((String) _keys.get(i)) + "] => [" + _values.get(i) + "]\n");
			outputFile.write("\n### PROPERTIES and PARAMETER sets :\"" + this._name + "\" ###\n");
			outputFile.close();
			// super.list(outputFile);
		} catch (IOException e) {
			Display.error("" + this.getClass().getName() + " \"" + this._name + "\" - cannot write logfile [" + __outputfilename + "]");
		}
	}

	/*
	 * public Object clone () { // try // { return super.clone(); // } // catch
	 * (CloneNotSupportedException e) // { // throw new
	 * InternalError(e.toString()); // } }
	 */

	/**
	 * check if all parameters are correctly initialised (debug purpose) -
	 * should be rewritten in inherited classes
	 */
	public void check() {
		/*
		 * int nb = 0; Display.info("\n# Checking Parameters integrity #"); if (
		 * _generations == -1 ) { Display.warning(" number of generations not
		 * set."); nb++; } if ( _initPopSize == -1 ) { Display.warning(" initial
		 * population size not set."); nb++; } if ( _selectionOperator == null ) {
		 * Display.warning(" Selection Operator not set."); nb++; } if (
		 * _initialisationOperator_Population == null ) { Display.warning("
		 * Population Initialisation Operator not set"); nb++; } if (
		 * _initialisationOperator_Individual == null ) { Display.warning("
		 * Individual Initialisation Operator not set"); nb++; } if (
		 * _evaluationOperator_Individual == null ) { Display.warning("
		 * Individual Evaluation Operator not set."); nb++; } if (
		 * _statisticsOperator_Population == null ) { Display.warning("
		 * Population Statistics Operator not set."); nb++; } if ( nb == 0 )
		 * Display.info("ok."); else Display.info(""+nb+" warning(s).");
		 */
	}

	/*
	 * // Lambda/mu parameters int LAMBDAPLUSMU = 0; int LAMBDACOMAMU = 1; //
	 * selection method int TOURNAMENTSELECTION = 0; int RANKSELECTION = 1; int
	 * RANDOMSELECTION = 2; // general-purpose parameters public int
	 * _currentGeneration = 0; public int _maxGenerations = 0; public int
	 * _populationSize = 0;
	 */

	/*
	 * // Lambda/mu parameters static int LAMBDAPLUSMU = 0; static int
	 * LAMBDACOMAMU = 1; // selection method static int TOURNAMENTSELECTION = 0;
	 * static int RANKSELECTION = 1; static int RANDOMSELECTION = 2;
	 */

}
